/*
 * Copyright 1995-2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package lib.java.net;

import gov.nist.siplite.address.URI;

import java.util.Vector;

import org.uni_hi.muus.server.util.Query;
import org.uni_hi.muus.util.HTTPUtils;

import lib.java.util.StringTokenizer;
import lib.sun.net.util.IPAddressUtil;

/**
 * Class <code>URL</code> represents a Uniform Resource
 * Locator, a pointer to a "resource" on the World
 * Wide Web. A resource can be something as simple as a file or a
 * directory, or it can be a reference to a more complicated object,
 * such as a query to a database or to a search engine. More
 * information on the types of URLs and their formats can be found at:
 * <blockquote>
 *     <a href="http://www.socs.uts.edu.au/MosaicDocs-old/url-primer.html">
 *    <i>http://www.socs.uts.edu.au/MosaicDocs-old/url-primer.html</i></a>
 * </blockquote>
 * <p>
 * In general, a URL can be broken into several parts. The previous
 * example of a URL indicates that the protocol to use is
 * <code>http</code> (HyperText Transfer Protocol) and that the
 * information resides on a host machine named
 * <code>www.socs.uts.edu.au</code>. The information on that host
 * machine is named <code>/MosaicDocs-old/url-primer.html</code>. The exact
 * meaning of this name on the host machine is both protocol
 * dependent and host dependent. The information normally resides in
 * a file, but it could be generated on the fly. This component of
 * the URL is called the <i>path</i> component.
 * <p>
 * A URL can optionally specify a "port", which is the
 * port number to which the TCP connection is made on the remote host
 * machine. If the port is not specified, the default port for
 * the protocol is used instead. For example, the default port for
 * <code>http</code> is <code>80</code>. An alternative port could be
 * specified as:
 * <blockquote><pre>
 *     http://www.socs.uts.edu.au:80/MosaicDocs-old/url-primer.html
 * </pre></blockquote>
 * <p>
 * The syntax of <code>URL</code> is defined by  <a
 * href="http://www.ietf.org/rfc/rfc2396.txt""><i>RFC&nbsp;2396: Uniform
 * Resource Identifiers (URI): Generic Syntax</i></a>, amended by <a
 * href="http://www.ietf.org/rfc/rfc2732.txt"><i>RFC&nbsp;2732: Format for
 * Literal IPv6 Addresses in URLs</i></a>. The Literal IPv6 address format
 * also supports scope_ids. The syntax and usage of scope_ids is described
 * <a href="Inet6Address.html#scoped">here</a>.
 * <p>
 * A URL may have appended to it a "fragment", also known
 * as a "ref" or a "reference". The fragment is indicated by the sharp
 * sign character "#" followed by more characters. For example,
 * <blockquote><pre>
 *     http://java.sun.com/index.html#chapter1
 * </pre></blockquote>
 * <p>
 * This fragment is not technically part of the URL. Rather, it
 * indicates that after the specified resource is retrieved, the
 * application is specifically interested in that part of the
 * document that has the tag <code>chapter1</code> attached to it. The
 * meaning of a tag is resource specific.
 * <p>
 * An application can also specify a "relative URL",
 * which contains only enough information to reach the resource
 * relative to another URL. Relative URLs are frequently used within
 * HTML pages. For example, if the contents of the URL:
 * <blockquote><pre>
 *     http://java.sun.com/index.html
 * </pre></blockquote>
 * contained within it the relative URL:
 * <blockquote><pre>
 *     FAQ.html
 * </pre></blockquote>
 * it would be a shorthand for:
 * <blockquote><pre>
 *     http://java.sun.com/FAQ.html
 * </pre></blockquote>
 * <p>
 * The relative URL need not specify all the components of a URL. If
 * the protocol, host name, or port number is missing, the value is
 * inherited from the fully specified URL. The file component must be
 * specified. The optional fragment is not inherited.
 * <p>
 * The URL class does not itself encode or decode any URL components
 * according to the escaping mechanism defined in RFC2396. It is the
 * responsibility of the caller to encode any fields, which need to be
 * escaped prior to calling URL, and also to decode any escaped fields,
 * that are returned from URL. Furthermore, because URL has no knowledge
 * of URL escaping, it does not recognise equivalence between the encoded
 * or decoded form of the same URL. For example, the two URLs:<br>
 * <pre>    http://foo.com/hello world/ and http://foo.com/hello%20world</pre>
 * would be considered not equal to each other.
 * <p>
 * Note, the {@link java.net.URI} class does perform escaping of its
 * component fields in certain circumstances. The recommended way
 * to manage the encoding and decoding of URLs is to use {@link java.net.URI},
 * and to convert between these two classes using {@link #toURI()} and
 * {@link URI#toURL()}.
 * <p>
 * The {@link URLEncoder} and {@link URLDecoder} classes can also be
 * used, but only for HTML form encoding, which is not the same
 * as the encoding scheme defined in RFC2396.
 *
 * @author  James Gosling
 * @version 1.144, 07/20/07
 * @since JDK1.0 
 */
public final class URL {

	/**
	 * The protocol to use (ftp, http, nntp, ... etc.) .
	 * @serial
	 */
	private String protocol;

	/**
	 * The host name to connect to.
	 * @serial
	 */
	private String host;

	/**
	 * The protocol port to connect to.
	 * @serial
	 */
	private int port = -1;

	/**
	 * The specified file name on that host. <code>file</code> is
	 * defined as <code>path[?query]</code>
	 * @serial
	 */
	private String file;
	
	/**
	 * The query part of this URL.
	 */
	private transient Query query;

	/**
	 * The authority part of this URL.
	 * @serial
	 */
	private String authority;
	
	/**
	 * The path part of this URL.
	 */
	private transient String path;

	/**
	 * The userinfo part of this URL.
	 */
	private transient String userInfo;

	/**
	 * # reference.
	 * @serial
	 */
	private String ref;

	/**
	 * The host's IP address, used in equals and hashCode.
	 * Computed on demand. An uninitialized or unknown hostAddress is null.
	 */
	transient String hostAddress;

	/* Our hash code.
	 * @serial
	 */
	private int hashCode = -1;

	private Vector _pathVector=null;

	private String extension;

	private static final Vector _prots = getValidProts();

	private static Vector getValidProts(){
		Vector v=new Vector();
		v.addElement("http");
		v.addElement("https");
		v.addElement("file");
		v.addElement("socket");
		v.addElement("mailto");
		return v;
	}

	/**
	 * Creates a URL from the specified <code>protocol</code>
	 * name, <code>host</code> name, and <code>file</code> name. The
	 * default port for the specified protocol is used.
	 * <p>
	 * This method is equivalent to calling the four-argument
	 * constructor with the arguments being <code>protocol</code>,
	 * <code>host</code>, <code>-1</code>, and <code>file</code>.
	 *
	 * No validation of the inputs is performed by this constructor.
	 *
	 * @param      protocol   the name of the protocol to use.
	 * @param      host       the name of the host.
	 * @param      file       the file on the host.
	 * @exception  MalformedURLException  if an unknown protocol is specified.
	 * @see        lib.java.net.URL#URL(java.lang.String, java.lang.String,
	 *			int, java.lang.String)
	 */
	public URL(String protocol, String host, String file)
	throws MalformedURLException {
		this (protocol, host, -1, file);
	}

	/**
	 * Creates a <code>URL</code> object from the specified
	 * <code>protocol</code>, <code>host</code>, <code>port</code>
	 * number, <code>file</code>, and <code>handler</code>. Specifying
	 * a <code>port</code> number of <code>-1</code> indicates that
	 * the URL should use the default port for the protocol. Specifying
	 * a <code>handler</code> of <code>null</code> indicates that the URL
	 * should use a default stream handler for the protocol, as outlined
	 * for:
	 *     java.net.URL#URL(java.lang.String, java.lang.String, int,
	 *                      java.lang.String)
	 *
	 * <p>If the handler is not null and there is a security manager,
	 * the security manager's <code>checkPermission</code>
	 * method is called with a
	 * <code>NetPermission("specifyStreamHandler")</code> permission.
	 * This may result in a SecurityException.
	 *
	 * No validation of the inputs is performed by this constructor.
	 *
	 * @param      protocol   the name of the protocol to use.
	 * @param      host       the name of the host.
	 * @param      port       the port number on the host.
	 * @param      file       the file on the host
	 * @param	   handler    the stream handler for the URL.
	 * @exception  MalformedURLException  if an unknown protocol is specified.
	 * @exception  SecurityException
	 *        if a security manager exists and its
	 *        <code>checkPermission</code> method doesn't allow
	 *        specifying a stream handler explicitly.
	 * @see        java.lang.System#getProperty(java.lang.String)
	 * @see        lib.java.net.URL#setURLStreamHandlerFactory(
	 *			lib.java.net.URLStreamHandlerFactory)
	 * @see        lib.java.net.URLStreamHandler
	 * @see        lib.java.net.URLStreamHandlerFactory#createURLStreamHandler(
	 *			java.lang.String)
	 * @see        SecurityManager#checkPermission
	 * @see        java.net.NetPermission
	 */
	public URL(String protocol, String host, int port, String file) throws MalformedURLException {

		protocol = protocol.toLowerCase();
		this .protocol = protocol;
		if (host != null) {

			/**
			 * if host is a literal IPv6 address,
			 * we will make it conform to RFC 2732
			 */
			if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
				host = "[" + host + "]";
			}
			this .host = host;

			if (port < -1) {
				throw new MalformedURLException("Invalid port number :"
						+ port);
			}
			this .port = port;
			authority = (port == -1) ? host : host + ":" + port;
		}

		Parts parts = new Parts(file);
		path = parts.getPath();
		extension=parts.getExtension();
		query = parts.getQuery();

		if (query != null) {
			this .file = path + "?" + query;
		} else {
			this .file = path;
		}
		ref = parts.getRef();
	}
	
	public URL(String spec) throws MalformedURLException{
		this(null, spec);
	}

	/**
	 * Creates a URL by parsing the given spec with the specified handler
	 * within a specified context. If the handler is null, the parsing
	 * occurs as with the two argument constructor.
	 *
	 * @param      context   the context in which to parse the specification.
	 * @param      spec      the <code>String</code> to parse as a URL.
	 * @param	   handler   the stream handler for the URL.
	 * @exception  MalformedURLException  if no protocol is specified, or an
	 *               unknown protocol is found, or <tt>spec</tt> is <tt>null</tt>.
	 * @exception  SecurityException
	 *        if a security manager exists and its
	 *        <code>checkPermission</code> method doesn't allow
	 *        specifying a stream handler.
	 * @see        lib.java.net.URL#URL(java.lang.String, java.lang.String,
	 *			int, java.lang.String)
	 * @see        lib.java.net.URLStreamHandler
	 * @see        lib.java.net.URLStreamHandler#parseURL(lib.java.net.URL,
	 *			java.lang.String, int, int)
	 */
	public URL(URL context, String spec)
	throws MalformedURLException {
		String original = HTTPUtils.decodeURL(spec);
		int i, limit, c;
		int start = 0;
		String newProtocol = null;
		boolean aRef = false;
		boolean isRelative = false;

		try {
			limit = spec.length();
			while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
				limit--; //eliminate trailing whitespace
			}
			while ((start < limit) && (spec.charAt(start) <= ' ')) {
				start++; // eliminate leading whitespace
			}

			if (spec.regionMatches(true, start, "url:", 0, 4)) {
				start += 4;
			}
			if (start < spec.length() && spec.charAt(start) == '#') {
				/* we're assuming this is a ref relative to the context URL.
				 * This means protocols cannot start w/ '#', but we must parse
				 * ref URL's like: "hello:there" w/ a ':' in them.
				 */
				aRef = true;
			}
			for (i = start; !aRef && (i < limit)
			&& ((c = spec.charAt(i)) != '/'); i++) {
				if (c == ':') {

					String s = spec.substring(start, i).toLowerCase();
					if (isValidProtocol(s)) {
						newProtocol = s;
						start = i + 1;
					}
					break;
				}
			}

			// Only use our context if the protocols match.
			protocol = newProtocol;
			if ((context != null)
					&& ((newProtocol == null) || newProtocol
							.equalsIgnoreCase(context.protocol))) {

				// If the context is a hierarchical URL scheme and the spec
				// contains a matching scheme then maintain backwards
				// compatibility and treat it as if the spec didn't contain
				// the scheme; see 5.2.3 of RFC2396
				if (context.path != null
						&& context.path.startsWith("/"))
					newProtocol = null;

				if (newProtocol == null) {
					protocol = context.protocol;
					authority = context.authority;
					userInfo = context.userInfo;
					host = context.host;
					port = context.port;
					file = context.file;
					path = context.path;
					isRelative = true;
				}
			}

			if (protocol == null) {
				throw new MalformedURLException("no protocol: "
						+ original);
			}

			i = spec.indexOf('#', start);
			if (i >= 0) {
				ref = spec.substring(i + 1, limit);
				limit = i;
			}

			/*
			 * Handle special case inheritance of query and fragment
			 * implied by RFC2396 section 5.2.2.
			 */
			if (isRelative && start == limit) {
				query = context.query;
				if (ref == null) {
					ref = context.ref;
				}
			}

			this.parseURL(this , spec, start, limit);

		} catch (MalformedURLException e) {
			throw e;
		} catch (Exception e) {
			MalformedURLException exception = new MalformedURLException(
					e.getMessage());
			//exception.initCause(e);
			throw exception;
		}
	}

	/*
	 * Returns true if specified string is a valid protocol name.
	 */
	private boolean isValidProtocol(String protocol) {
		int len = protocol.length();
		if (len < 1)
			return false;
		return _prots .contains(protocol);
		//		char c = protocol.charAt(0);
		//		if (!Character.isLetter(c))
		//			return false;
		//		for (int i = 1; i < len; i++) {
		//			c = protocol.charAt(i);
		//			if (!Character.isLetterOrDigit(c) && c != '.' && c != '+'
		//				&& c != '-') {
		//				return false;
		//			}
		//		}
		//		return true;
	}

	/**
	 * Sets the fields of the URL. This is not a public method so that
	 * only URLStreamHandlers can modify URL fields. URLs are
	 * otherwise constant.
	 *
	 * @param protocol the name of the protocol to use
	 * @param host the name of the host
               @param port the port number on the host
	 * @param file the file on the host
	 * @param ref the internal reference in the URL
	 */
	protected void set(String protocol, String host, int port,
			String file, String ref) {
		synchronized (this ) {
			this .protocol = protocol;
			this .host = host;
			authority = port == -1 ? host : host + ":" + port;
			this .port = port;
			this .file = file;
			this .ref = ref;
			/* This is very important. We must recompute this after the
			 * URL has been changed. */
			hashCode = -1;
			hostAddress = null;
			int q = file.lastIndexOf('?');
			if (q != -1) {
				query = new Query(file.substring(q + 1));
				path = file.substring(0, q);
			} else
				path = file;
		}
	}

	/**
	 * Sets the specified 8 fields of the URL. This is not a public method so
	 * that only URLStreamHandlers can modify URL fields. URLs are otherwise
	 * constant.
	 *
	 * @param protocol the name of the protocol to use
	 * @param host the name of the host
	 * @param port the port number on the host
	 * @param authority the authority part for the url
	 * @param userInfo the username and password
	 * @param path the file on the host
	 * @param ref the internal reference in the URL
	 * @param query the query part of this URL
	 * @since 1.3
	 */
	protected void set(String protocol, String host, int port,
			String authority, String userInfo, String path,
			Query query, String ref) {
		synchronized (this ) {
			this .protocol = protocol;
			this .host = host;
			this .port = port;
			this .file = query == null ? path : path + "?" + query;
			this .userInfo = userInfo;
			this .path = path;
			this .ref = ref;
			/* This is very important. We must recompute this after the
			 * URL has been changed. */
			hashCode = -1;
			hostAddress = null;
			this .query = query;
			this .authority = authority;
		}
	}

	/**
	 * Gets the query part of this <code>URL</code>.
	 *
	 * @return  the query part of this <code>URL</code>, 
	 * or <CODE>null</CODE> if one does not exist
	 * @since 1.3
	 */
	public Query getQuery() {
		return query;
	}

	/**
	 * Gets the path part of this <code>URL</code>.
	 *
	 * @return  the path part of this <code>URL</code>, or an
	 * empty string if one does not exist
	 * @since 1.3
	 */
	public String getPath() {
		return path;
	}
	
	/**
	 * Returns the file extension.
	 * @return the file extension as string.
	 */
	public String getFileExtension(){
		return extension;
	}
	
	/**
	 * Returns the path without the first folder,
	 * as this should be the Bluetooth-address.
	 * @return path without the first folder
	 */
	public String getLocalPath(){
		int i=path.indexOf("/", 1);
		String result=null;
		result=path.substring(i, path.length());
		return result;
	}
	
	
	public String getFirstPathEntry(){
		int i=path.indexOf("/", 1);
		String result=null;
		result=path.substring(1, i==-1?path.length():i);
		return result;
	}
	
	public Vector getPathVector(){
		if(_pathVector==null){
			StringTokenizer st=new StringTokenizer(path,"/");
			_pathVector=new Vector(st.countTokens());
			while(st.hasMoreTokens()){
				_pathVector.addElement(st.nextToken());
			}
		}
		return _pathVector;
	}

	/**
	 * Gets the userInfo part of this <code>URL</code>.
	 *
	 * @return  the userInfo part of this <code>URL</code>, or 
	 * <CODE>null</CODE> if one does not exist
	 * @since 1.3
	 */
	public String getUserInfo() {
		return userInfo;
	}

	/**
	 * Gets the authority part of this <code>URL</code>.
	 *
	 * @return  the authority part of this <code>URL</code>
	 * @since 1.3
	 */
	public String getAuthority() {
		return authority;
	}

	/**
	 * Gets the port number of this <code>URL</code>.
	 *
	 * @return  the port number, or -1 if the port is not set
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Gets the protocol name of this <code>URL</code>.
	 *
	 * @return  the protocol of this <code>URL</code>.
	 */
	public String getProtocol() {
		return protocol;
	}

	/**
	 * Gets the host name of this <code>URL</code>, if applicable.
	 * The format of the host conforms to RFC 2732, i.e. for a
	 * literal IPv6 address, this method will return the IPv6 address
	 * enclosed in square brackets (<tt>'['</tt> and <tt>']'</tt>).
	 *
	 * @return  the host name of this <code>URL</code>.
	 */
	public String getHost() {
		return host;
	}

	/**
	 * Gets the file name of this <code>URL</code>.
	 * The returned file portion will be
	 * the same as <CODE>getPath()</CODE>, plus the concatenation of
	 * the value of <CODE>getQuery()</CODE>, if any. If there is 
	 * no query portion, this method and <CODE>getPath()</CODE> will
	 * return identical results.
	 *
	 * @return  the file name of this <code>URL</code>,
	 * or an empty string if one does not exist
	 */
	public String getFile() {
		return file;
	}

	/**
	 * Gets the anchor (also known as the "reference") of this
	 * <code>URL</code>.
	 *
	 * @return  the anchor (also known as the "reference") of this
	 *          <code>URL</code>, or <CODE>null</CODE> if one does not exist
	 */
	public String getRef() {
		return ref;
	}

	/**
	 * Compares this URL for equality with another object.<p>
	 *
	 * If the given object is not a URL then this method immediately returns
	 * <code>false</code>.<p>
	 *
	 * Two URL objects are equal if they have the same protocol, reference
	 * equivalent hosts, have the same port number on the host, and the same
	 * file and fragment of the file.<p>
	 *
	 * Two hosts are considered equivalent if both host names can be resolved
	 * into the same IP addresses; else if either host name can't be
	 * resolved, the host names must be equal without regard to case; or both
	 * host names equal to null.<p>
	 *
	 * Since hosts comparison requires name resolution, this operation is a
	 * blocking operation. <p>
	 *
	 * Note: The defined behavior for <code>equals</code> is known to
	 * be inconsistent with virtual hosting in HTTP.
	 *
	 * @param   obj   the URL to compare against.
	 * @return  <code>true</code> if the objects are the same;
	 *          <code>false</code> otherwise.
	 */
	public boolean equals(Object obj) {
		if (!(obj instanceof  URL))
			return false;
		URL u2 = (URL) obj;

		return this.equals(this , u2);
	}

	/**
	 * Creates an integer suitable for hash table indexing.<p>
	 * 
	 * The hash code is based upon all the URL components relevant for URL
	 * comparison. As such, this operation is a blocking operation.<p>
	 *
	 * @return  a hash code for this <code>URL</code>.
	 */
	public synchronized int hashCode() {
		if (hashCode != -1)
			return hashCode;

		hashCode = this.hashCode(this );
		return hashCode;
	}

	/**
	 * Compares two URLs, excluding the fragment component.<p>
	 *
	 * Returns <code>true</code> if this <code>URL</code> and the
	 * <code>other</code> argument are equal without taking the
	 * fragment component into consideration.
	 *
	 * @param   other   the <code>URL</code> to compare against.
	 * @return  <code>true</code> if they reference the same remote object;
	 *          <code>false</code> otherwise.
	 */
	public boolean sameFile(URL other) {
		return this.sameFile(this , other);
	}

	/**
	 * Constructs a string representation of this <code>URL</code>. The
	 * string is created by calling the <code>toExternalForm</code>
	 * method of the stream protocol handler for this object.
	 *
	 * @return  a string representation of this object.
	 * @see     lib.java.net.URL#URL(java.lang.String, java.lang.String, int,
	 *			java.lang.String)
	 * @see     lib.java.net.URLStreamHandler#toExternalForm(lib.java.net.URL)
	 */
	public String toString() {
		return toExternalForm();
	}

	/**
	 * Constructs a string representation of this <code>URL</code>. The
	 * string is created by calling the <code>toExternalForm</code>
	 * method of the stream protocol handler for this object.
	 *
	 * @return  a string representation of this object.
	 * @see     lib.java.net.URL#URL(java.lang.String, java.lang.String,
	 *			int, java.lang.String)
	 * @see     lib.java.net.URLStreamHandler#toExternalForm(lib.java.net.URL)
	 */
	public String toExternalForm() {
		return this.toExternalForm(this );
	}
	
	/**
	 * Parses the string representation of a <code>URL</code> into a
	 * <code>URL</code> object.
	 * <p>
	 * If there is any inherited context, then it has already been
	 * copied into the <code>URL</code> argument.
	 * <p>
	 * The <code>parseURL</code> method of <code>URLStreamHandler</code>
	 * parses the string representation as if it were an
	 * <code>http</code> specification. Most URL protocol families have a
	 * similar parsing. A stream protocol handler for a protocol that has
	 * a different syntax must override this routine.
	 *
	 * @param   u       the <code>URL</code> to receive the result of parsing
	 *                  the spec.
	 * @param   spec    the <code>String</code> representing the URL that
	 *                  must be parsed.
	 * @param   start   the character index at which to begin parsing. This is
	 *                  just past the '<code>:</code>' (if there is one) that
	 *                  specifies the determination of the protocol name.
	 * @param   limit   the character position to stop parsing at. This is the
	 *                  end of the string or the position of the
	 *                  "<code>#</code>" character, if present. All information
	 *                  after the sharp sign indicates an anchor.
	 */
	protected void parseURL(URL u, String spec, int start, int limit) {
		// These fields may receive context content if this was relative URL
		String protocol = u.getProtocol();
		String authority = u.getAuthority();
		String userInfo = u.getUserInfo();
		String host = u.getHost();
		int port = u.getPort();
		String path = u.getPath();
		Query query = u.getQuery();

		// This field has already been parsed
		String ref = u.getRef();

		boolean isRelPath = false;
		boolean queryOnly = false;

		// FIX: should not assume query if opaque
		// Strip off the query part
		if (start < limit) {
			int queryStart = spec.indexOf('?');
			queryOnly = queryStart == start;
			if ((queryStart != -1) && (queryStart < limit)) {
				query=new Query(spec.substring(queryStart + 1, limit));
				if (limit > queryStart)
					limit = queryStart;
				spec = spec.substring(0, queryStart);
			}
		}

		int i = 0;
		// Parse the authority part if any
		boolean isUNCName = (start <= limit - 4)
		&& (spec.charAt(start) == '/')
		&& (spec.charAt(start + 1) == '/')
		&& (spec.charAt(start + 2) == '/')
		&& (spec.charAt(start + 3) == '/');
		if (!isUNCName && (start <= limit - 2)
				&& (spec.charAt(start) == '/')
				&& (spec.charAt(start + 1) == '/')) {
			start += 2;
			i = spec.indexOf('/', start);
			if (i < 0) {
				i = spec.indexOf('?', start);
				if (i < 0)
					i = limit;
			}

			host = authority = spec.substring(start, i);

			int ind = authority.indexOf('@');
			if (ind != -1) {
				userInfo = authority.substring(0, ind);
				host = authority.substring(ind + 1);
			} else {
				userInfo = null;
			}
			if (host != null) {
				// If the host is surrounded by [ and ] then its an IPv6 
				// literal address as specified in RFC2732
				if (host.length() > 0 && (host.charAt(0) == '[')) {
					if ((ind = host.indexOf(']')) > 2) {

						String nhost = host;
						host = nhost.substring(0, ind + 1);
						if (!IPAddressUtil.isIPv6LiteralAddress(host
								.substring(1, ind))) {
							throw new IllegalArgumentException(
									"Invalid host: " + host);
						}

						port = -1;
						if (nhost.length() > ind + 1) {
							if (nhost.charAt(ind + 1) == ':') {
								++ind;
								// port can be null according to RFC2396
								if (nhost.length() > (ind + 1)) {
									port = Integer.parseInt(nhost
											.substring(ind + 1));
								}
							} else {
								throw new IllegalArgumentException(
										"Invalid authority field: "
										+ authority);
							}
						}
					} else {
						throw new IllegalArgumentException(
								"Invalid authority field: " + authority);
					}
				} else {
					ind = host.indexOf(':');
					port = -1;
					if (ind >= 0) {
						// port can be null according to RFC2396
						if (host.length() > (ind + 1)) {
							port = Integer.parseInt(host
									.substring(ind + 1));
						}
						host = host.substring(0, ind);
					}
				}
			} else {
				host = "";
			}
			if (port < -1)
				throw new IllegalArgumentException(
						"Invalid port number :" + port);
			start = i;
			// If the authority is defined then the path is defined by the
			// spec only; See RFC 2396 Section 5.2.4.
			if (authority != null && authority.length() > 0)
				path = "";
		}

		if (host == null) {
			host = "";
		}

		// Parse the file path if any
		if (start < limit) {
			if (spec.charAt(start) == '/') {
				path = spec.substring(start, limit);
			} else if (path != null && path.length() > 0) {
				isRelPath = true;
				int ind = path.lastIndexOf('/');
				String seperator = "";
				if (ind == -1 && authority != null)
					seperator = "/";
				path = path.substring(0, ind + 1) + seperator
				+ spec.substring(start, limit);

			} else {
				String seperator = (authority != null) ? "/" : "";
				path = seperator + spec.substring(start, limit);
			}
		} else if (queryOnly && path != null) {
			int ind = path.lastIndexOf('/');
			if (ind < 0)
				ind = 0;
			path = path.substring(0, ind) + "/";
		}
		if (path == null)
			path = "";

		if (isRelPath) {
			// Remove embedded /./
			while ((i = path.indexOf("/./")) >= 0) {
				path = path.substring(0, i) + path.substring(i + 2);
			}
			// Remove embedded /../ if possible
			i = 0;
			while ((i = path.indexOf("/../", i)) >= 0) {
				/* 
				 * A "/../" will cancel the previous segment and itself, 
				 * unless that segment is a "/../" itself
				 * i.e. "/a/b/../c" becomes "/a/c"
				 * but "/../../a" should stay unchanged
				 */
				if (i > 0
						&& (limit = path.lastIndexOf('/', i - 1)) >= 0
						&& (path.indexOf("/../", limit) != 0)) {
					path = path.substring(0, limit)
					+ path.substring(i + 3);
					i = 0;
				} else {
					i = i + 3;
				}
			}
			// Remove trailing .. if possible
			while (path.endsWith("/..")) {
				i = path.indexOf("/..");
				if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
					path = path.substring(0, limit + 1);
				} else {
					break;
				}
			}
			// Remove starting .
			if (path.startsWith("./") && path.length() > 2)
				path = path.substring(2);

			// Remove trailing .
			if (path.endsWith("/."))
				path = path.substring(0, path.length() - 1);
		}

		setURL(u, protocol, host, port, authority, userInfo, path,
				query, ref);
	}

	/**
	 * Returns the default port for a URL parsed by this handler. This method
	 * is meant to be overidden by handlers with default port numbers.
	 * @return the default port for a <code>URL</code> parsed by this handler.
	 * @since 1.3
	 */
	protected int getDefaultPort() {
		return -1;
	}

	/**
	 * Provides the default equals calculation. May be overidden by handlers
	 * for other protocols that have different requirements for equals().
	 * This method requires that none of its arguments is null. This is 
	 * guaranteed by the fact that it is only called by java.net.URL class.
	 * @param u1 a URL object
	 * @param u2 a URL object
	 * @return <tt>true</tt> if the two urls are 
	 * considered equal, ie. they refer to the same 
	 * fragment in the same file.
	 * @since 1.3
	 */
	protected boolean equals(URL u1, URL u2) {
		String ref1 = u1.getRef();
		String ref2 = u2.getRef();
		return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2)))
		&& sameFile(u1, u2);
	}

	/**
	 * Provides the default hash calculation. May be overidden by handlers for
	 * other protocols that have different requirements for hashCode
	 * calculation.
	 * @param u a URL object
	 * @return an <tt>int</tt> suitable for hash table indexing
	 * @since 1.3
	 */
	protected int hashCode(URL u) {
		int h = 0;

		// Generate the protocol part.
		String protocol = u.getProtocol();
		if (protocol != null)
			h += protocol.hashCode();

		// Generate the host part.
		String addr = getHostAddress(u);
		if (addr != null) {
			h += addr.hashCode();
		} else {
			String host = u.getHost();
			if (host != null)
				h += host.toLowerCase().hashCode();
		}

		// Generate the file part.
		String file = u.getFile();
		if (file != null)
			h += file.hashCode();

		// Generate the port part.
		if (u.getPort() == -1)
			h += getDefaultPort();
		else
			h += u.getPort();

		// Generate the ref part.
		String ref = u.getRef();
		if (ref != null)
			h += ref.hashCode();

		return h;
	}

	/**
	 * Compare two urls to see whether they refer to the same file,
	 * i.e., having the same protocol, host, port, and path.
	 * This method requires that none of its arguments is null. This is 
	 * guaranteed by the fact that it is only called indirectly
	 * by java.net.URL class.
	 * @param u1 a URL object
	 * @param u2 a URL object
	 * @return true if u1 and u2 refer to the same file
	 * @since 1.3
	 */
	protected boolean sameFile(URL u1, URL u2) {
		// Compare the protocols.
		if (!((u1.getProtocol() == u2.getProtocol()) || (u1
				.getProtocol() != null && u1.getProtocol()
				.equalsIgnoreCase(u2.getProtocol()))))
			return false;

		// Compare the files.
		if (!(u1.getFile() == u2.getFile() || (u1.getFile() != null && u1
				.getFile().equals(u2.getFile()))))
			return false;

		// Compare the ports.
		int port1, port2;
		port1 = (u1.getPort() != -1) ? u1.getPort() : 80;
		port2 = (u2.getPort() != -1) ? u2.getPort() : 80;
		if (port1 != port2)
			return false;

		// Compare the hosts.
		if (!hostsEqual(u1, u2))
			return false;

		return true;
	}

	/**
	 * Get the IP address of our host. An empty host field or a DNS failure
	 * will result in a null return.
	 *
	 * @param u a URL object
	 * @return an <code>InetAddress</code> representing the host
	 * IP address.
	 * @since 1.3
	 */
	protected synchronized String getHostAddress(URL u) {
		return u.hostAddress;
	}

	/**
	 * Compares the host components of two URLs.
	 * @param u1 the URL of the first host to compare 
	 * @param u2 the URL of the second host to compare 
	 * @return	<tt>true</tt> if and only if they 
	 * are equal, <tt>false</tt> otherwise.
	 * @since 1.3
	 */
	protected boolean hostsEqual(URL u1, URL u2) {
		String a1 = getHostAddress(u1);
		String a2 = getHostAddress(u2);
		// if we have internet address for both, compare them
		if (a1 != null && a2 != null) {
			return a1.equals(a2);
			// else, if both have host names, compare them
		} else if (u1.getHost() != null && u2.getHost() != null)
			return u1.getHost().equalsIgnoreCase(u2.getHost());
		else
			return u1.getHost() == null && u2.getHost() == null;
	}

	/**
	 * Converts a <code>URL</code> of a specific protocol to a
	 * <code>String</code>.
	 *
	 * @param   u   the URL.
	 * @return  a string representation of the <code>URL</code> argument.
	 */
	protected String toExternalForm(URL u) {

		// pre-compute length of StringBuffer
		int len = u.getProtocol().length() + 1;
		if (u.getAuthority() != null && u.getAuthority().length() > 0)
			len += 2 + u.getAuthority().length();
		if (u.getPath() != null) {
			len += u.getPath().length();
		}
		if (u.getQuery() != null) {
			len += 1 + u.getQuery().size();
		}
		if (u.getRef() != null)
			len += 1 + u.getRef().length();

		StringBuffer result = new StringBuffer(len);
		result.append(u.getProtocol());
		result.append(":");
		if (u.getAuthority() != null && u.getAuthority().length() > 0) {
			result.append("//");
			result.append(u.getAuthority());
		}
		if (u.getPath() != null) {
			result.append(u.getPath());
		}
		if (u.getQuery() != null) {
			result.append('?');
			result.append(u.getQuery());
		}
		if (u.getRef() != null) {
			result.append("#");
			result.append(u.getRef());
		}
		return result.toString();
	}

	/**
	 * Sets the fields of the <code>URL</code> argument to the indicated values.
	 * Only classes derived from URLStreamHandler are supposed to be able
	 * to call the set method on a URL.
	 *
	 * @param   u         the URL to modify.
	 * @param   protocol  the protocol name.
	 * @param   host      the remote host value for the URL.
	 * @param   port      the port on the remote machine.
	 * @param   authority the authority part for the URL.
	 * @param   userInfo the userInfo part of the URL.
	 * @param   path      the path component of the URL. 
	 * @param   query     the query part for the URL.
	 * @param   ref       the reference.
	 * @exception	SecurityException	if the protocol handler of the URL is 
	 *					different from this one
	 * @see     lib.java.net.URL#set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String)
	 * @since 1.3
	 */
	protected void setURL(URL u, String protocol, String host,
			int port, String authority, String userInfo, String path,
			String query, String ref) {
		// ensure that no one can reset the protocol on a given URL.
		u.set(u.getProtocol(), host, port, authority, userInfo, path,
				new Query(query), ref);
	}
	
	/**
	 * Sets the fields of the <code>URL</code> argument to the indicated values.
	 * Only classes derived from URLStreamHandler are supposed to be able
	 * to call the set method on a URL.
	 *
	 * @param   u         the URL to modify.
	 * @param   protocol  the protocol name.
	 * @param   host      the remote host value for the URL.
	 * @param   port      the port on the remote machine.
	 * @param   authority the authority part for the URL.
	 * @param   userInfo the userInfo part of the URL.
	 * @param   path      the path component of the URL. 
	 * @param   query     the query part for the URL.
	 * @param   ref       the reference.
	 * @exception	SecurityException	if the protocol handler of the URL is 
	 *					different from this one
	 * @see     lib.java.net.URL#set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String)
	 * @since 1.3
	 */
	protected void setURL(URL u, String protocol, String host,
			int port, String authority, String userInfo, String path,
			Query query, String ref) {
		// ensure that no one can reset the protocol on a given URL.
		u.set(u.getProtocol(), host, port, authority, userInfo, path,
				query, ref);
	}
}

class Parts {
	String path, ref, query, extension;

	Parts(String file) {
		int ind = file.indexOf('#');
		ref = ind < 0 ? null : file.substring(ind + 1);
		file = ind < 0 ? file : file.substring(0, ind);
		int q = file.lastIndexOf('?');
		if (q != -1) {
			query = file.substring(q + 1);
			path = file.substring(0, q);
		} else {
			path = file;
		}
		ind=path.lastIndexOf('.');
		extension=ind<0?"":path.substring(ind+1);
	}
	
	String getExtension(){
		return extension;
	}

	String getPath() {
		return path;
	}

	Query getQuery() {
		return (query==null ? null : new Query(query));
	}
	
	String getQueryString(){
		return query;
	}

	String getRef() {
		return ref;
	}
}